// PCMHelper - A utility script for PowerCards creates attributes to facilitate PowerCard template and
// replacements style cards. .2Will monitor creation of objects and changes to characters to
// try to keep the attributes updated so there is no additional work involved on the DMs
// part to set up new creatures or character abilities.
//
// The following attributes are created on characters (PCs and/or NPCs) as appropriate:
// attacklist = list of PC Attacks (center of the character sheet). ONLY those marked as attacks.
// npcactionlist = list of repeating_npcaction entries (under the "Actions" heading on the sheet)
// npclegendaryactions = list of repeating_npcaction-l entries
// cantriplist = list of known cantrips
// l1_spell_list thru l9_spell_list = list of known spells.
//
// Supported Commands:
// !pcm Runs the attribute evaluation for any selected tokens.
// !pcmeverybody Runs the attribute evaluation for ALL characters in the game. Good for adding the system
// to an existing game without needing to select each character. Might take a while to run
// on large campaigns (and might even fail).
// !pcmqueue Processes the current queue (if any) - should never be needed.
// !pcmsetup Create (or recreate) the Replacements, Templates, and Macros used by PCMHelper
//
var PCMHelper = PCMHelper || (function() {
var PCMHelper_Author = "Kurt Jaegers";
var PCMHelper_Version = "1.0.5";
var PCMHelper_LastUpdated = "2019-02-03";
var pc_replacements = `Advantage:{{query=1}} {{normal=1}} {{r2=[[0d20|1d20;{{query=1}} {{advantage=1}} {{r2=[[1d20|2d20kh1;{{query=1}} {{disadvantage=1}} {{r2=[[1d20|2d20kl1`;
var pc_templates = `Basics: --tokenid|~0! --target_list|~1! --emote|~S-CN$ ~2! against ~T-CN$ --format|~S-CN$ --name|~3! --leftsub|~4! --rightsub|~5!
NPCBasics: --tokenid|~0! --target_list|~1! --emote|~S-CN$ ~2! against ~T-CN$ --format|badguys --name|~3! --['~NPCA-TYPE$' -eq 'ATTACK']leftsub|~4! --['~NPCA-RANGE$' -eq 'ATTACK']rightsub|~5!
NPCAttack: --["~NPCA-TYPE$" -eq "ATTACK"]Attack:|[#[ [$Atk] ~0! + ~NPCA-TOHIT$ [Attack Bonus] ]#] vs **AC** [#[~T-AC$]#] --["~NPCA-TYPE$" -eq "ATTACK"]?? $Atk < ~T-AC$ ?? !Missed|**The ~NPCA-NAME$ attack missed!** --["~NPCA-TYPE$" -eq "ATTACK" -and "~NPCA-DAMAGETYPE2$" -eq ""]?? $Atk >= ~T-AC$ AND $Atk.base <> 20 ?? Hit:|[#[ [$Dmg] ~NPCA-DAMAGE$ ]#] ~NPCA-DAMAGETYPE$ damage --["~NPCA-TYPE$" -eq "ATTACK" -and "~NPCA-DAMAGETYPE2$" -ne ""]?? $Atk >= ~T-AC$ AND $Atk.base <> 20 ?? Hit:|[#[ [$Dmg] ~NPCA-DAMAGE$ ]#] ~NPCA-DAMAGETYPE$ damage and [#[ [$Dmg2] ~NPCA-DAMAGE2$ ]#] ~NPCA-DAMAGETYPE2$ damage --["~NPCA-TYPE$" -eq "ATTACK" -and "~NPCA-DAMAGETYPE2$" -eq ""]?? $Atk.base == 20 ?? Hit:|[#[ [$Dmg] ~NPCA-CRIT$ ]#] ~NPCA-DAMAGETYPE$ damage --["~NPCA-TYPE$" -eq "ATTACK" -and "~NPCA-DAMAGETYPE2$" -ne ""]?? $Atk.base == 20 ?? Hit:|[#[ [$Dmg] ~NPCA-CRIT$ ]#] ~NPCA-DAMAGETYPE$ damage and [#[ [$Dmg2] ~NPCA-CRIT2$ ]#] ~NPCA-DAMAGETYPE2$ damage --["~NPCA-TYPE$" -eq "ATTACK"]?? $Atk.base == 1 ?? Fumble|The attack goes horribly wrong! --["~NPCA-DESCRIPTION$" -ne "" ]Description|~NPCA-DESCRIPTION$
PCAttack: --Attack|[#[ [$Atk] ~0! + ~PCA-ATKBONUS$ [Attack Bonus] ]#] vs **AC** [#[~T-AC$]#] --?? $Atk.base == 1 ?? Fumble|The attack went horribly wrong! --?? $Atk.base <> 1 AND $Atk.base <> 20 AND $Atk.total < ~T-AC$ ?? Miss|~S-CN$ missed. --["~PCA-DMG2DICE$" -eq "none"]?? $Atk.base <> 20 AND $Atk.total >= ~T-AC$ ?? Hit|~S-CN$ hits ~T-CN$ for [#[ [$Dmg] ~PCA-DMG1DICE$ ]] ~PCA-DMG1TYPE$ damage --["~PCA-DMG2DICE$" -ne "none"]?? $Atk.base <> 20 AND $Atk.total >= ~T-AC$ ?? Hit|~S-CN$ hits ~T-CN$ for [#[ [$Dmg] ~PCA-DMG1DICE$ ]] ~PCA-DMG1TYPE$ damage and [[ [$Dmg2] ~PCA-DMG2DICE$ ]] ~PCA-DMG2TYPE$ damage --["~PCA-DMG2DICE$" -eq "none"]?? $Atk.base == 20 AND $Atk.total >= ~T-AC$ ?? Critical Hit|~S-CN$ hits ~T-CN$ for [#[ [$Dmg] ~PCA-DMG1CRIT$ ]] ~PCA-DMG1TYPE$ damage --["~PCA-DMG2DICE$" -ne "none"]?? $Atk.base == 20 AND $Atk.total >= ~T-AC$ ?? Critical Hit|~S-CN$ hits ~T-CN$ for [#[ [$Dmg] ~PCA-DMG1CRIT$ ]] ~PCA-DMG1TYPE$ damage and [[ [$Dmg2] ~PCA-DMG2CRIT$ ]] ~PCA-DMG2TYPE$ damage --["~PCA-SAVE$" -eq "true"]?? $Atk.base == 20 OR $Atk.total >= ~T-AC$ ?? Save|~T-CN$ can attempt a **DC ~PCA-SAVEDC$** ~PCA-SAVEATTR$ saving throw for ~PCA-SAVEEFFECT$ --["~PCA-DESC$" -ne "none"]Description|~PCA-DESC$
CantripAttack: --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '']Attack:|[#[ [$Atk] ~0! + ~S-SAB$ [Spell Attack] ]#] vs **AC** [#[~T-AC$]#] --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '']?? $Atk < ~T-AC$ ?? !Missed|**You missed!** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '' -and ~SP-MORECANTRIPDAMAGE$ -eq 0]?? $Atk >= ~T-AC$ AND $Atk.base <> 20 ?? Hit:|[#[ [$Dmg] (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ]#] ~SP-DAMAGETYPE$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '' -and ~S-L$ -lt 4 -and ~SP-MORECANTRIPDAMAGE$ -eq 1]?? $Atk >= ~T-AC$ AND $Atk.base <> 20 ?? Hit:|[#[ [$Dmg] (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ]#] ~SP-DAMAGETYPE$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '' -and ~SP-MORECANTRIPDAMAGE$ -eq 1 -and ~S-L$ -gt 4 -and ~S-L$ -lt 12]?? $Atk >= ~T-AC$ AND $Atk.base <> 20 ?? Hit:|[#[ [$Dmg] (~SP-DAMAGE$) + (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ]#] ~SP-DAMAGETYPE$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '' -and ~SP-MORECANTRIPDAMAGE$ -eq 1 -and ~S-L$ -gt 11 -and ~S-L$ -lt 17]?? $Atk >= ~T-AC$ AND $Atk.base <> 20 ?? Hit:|[#[ [$Dmg] (~SP-DAMAGE$) + (~SP-DAMAGE$) + (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ]#] ~SP-DAMAGETYPE$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '' -and ~SP-MORECANTRIPDAMAGE$ -eq 1 -and ~S-L$ -gt 16 ]?? $Atk -ge ~T-AC$ AND $Atk.base <> 20 ?? Hit:|[#[ [$Dmg] (~SP-DAMAGE$) + (~SP-DAMAGE$) + (~SP-DAMAGE$) + (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ~SP-DAMAGETYPE$ ]#] damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '' -and ~SP-MORECANTRIPDAMAGE$ -eq 0]?? $Atk.base == 20 ?? Critical Hit:|[#[ [$CritDmg] (~SP-DAMAGE$) [Base] + (~SP-DAMAGE$) [Crit] ]#] ~SP-DAMAGETYPE$ Damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '' -and ~S-L$ -lt 4 -and ~SP-MORECANTRIPDAMAGE$ -eq 1]?? $Atk.base == 20 ?? Critical Hit:|[#[ [$CritDmg] (~SP-DAMAGE$) [Base] + (~SP-DAMAGE$) [Crit] ]#] ~SP-DAMAGETYPE$ Damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '' -and ~SP-MORECANTRIPDAMAGE$ -eq 1 -and ~S-L$ -gt 4 -and ~S-L$ -lt 12]?? $Atk.base == 20 ?? Critical Hit:|[#[ [$CritDmg] (~SP-DAMAGE$) + (~SP-DAMAGE$) [Base] + (~SP-DAMAGE$) + (~SP-DAMAGE$) [Crit] ]#] ~SP-DAMAGETYPE$ Damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '' -and ~SP-MORECANTRIPDAMAGE$ -eq 1 -and ~S-L$ -gt 11 -and ~S-L$ -lt 17]?? $Atk.base == 20 ?? Critical Hit:|[#[ [$CritDmg] (~SP-DAMAGE$) + (~SP-DAMAGE$) + (~SP-DAMAGE$) [Base] + (~SP-DAMAGE$) + (~SP-DAMAGE$) + (~SP-DAMAGE$) [Crit] ]#] ~SP-DAMAGETYPE$ Damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '' -and ~SP-MORECANTRIPDAMAGE$ -eq 1 -and ~S-L$ -gt 16 ]?? $Atk.base == 20 ?? Critical Hit:|[#[ [$CritDmg] (~SP-DAMAGE$) + (~SP-DAMAGE$) + (~SP-DAMAGE$) + (~SP-DAMAGE$) [Base] + (~SP-DAMAGE$) + (~SP-DAMAGE$) + (~SP-DAMAGE$) + (~SP-DAMAGE$) [Crit] ]#] ~SP-DAMAGETYPE$ Damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq '']?? $Atk.base == 1 ?? !|**Fumble:** Spell goes horribly wrong! --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq 'Strength']Save|~T-CN$ rolls [[ [$Save] 1d20 + ~T-SBSTR$ ]] on a ~SP-SAVE$ save vs **DC ~S-SSDC$** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq 'Dexterity']Save|~T-CN$ rolls [[ [$Save] 1d20 + ~T-SBDEX$ ]] on a ~SP-SAVE$ save vs **DC ~S-SSDC$** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq 'Constitution']Save|~T-CN$ rolls [[ [$Save] 1d20 + ~T-SBCON$ ]] on a ~SP-SAVE$ save vs **DC ~S-SSDC$** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq 'Intelligence']Save|~T-CN$ rolls [[ [$Save] 1d20 + ~T-SBINT$ ]] on a ~SP-SAVE$ save vs **DC ~S-SSDC$** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq 'Wisdom']Save|~T-CN$ rolls [[ [$Save] 1d20 + ~T-SBWIS$ ]] on a ~SP-SAVE$ save vs **DC ~S-SSDC$** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq 'Charisma']Save|~T-CN$ rolls [[ [$Save] 1d20 + ~T-SBCHA$ ]] on a ~SP-SAVE$ save vs **DC ~S-SSDC$** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -ne '' -and ~S-L$ -lt 4 -and ~SP-MORECANTRIPDAMAGE$ -ne 0]?? $Save < ~S-SSDC$ ?? Save Failed|~T-CN$ takes [#[ [$Dmg] (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ]#] ~SP-DAMAGETYPE$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -ne '' -and ~SP-MORECANTRIPDAMAGE$ -eq 0]?? $Save < ~S-SSDC$ ?? Save Failed|~T-CN$ takes [#[ [$Dmg] (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ]#] ~SP-DAMAGETYPE$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -ne '' -and ~SP-MORECANTRIPDAMAGE$ -eq 1 -and ~S-L$ -gt 4 -and ~S-L$ -lt 12]?? $Save < ~S-SSDC$ ?? Save Failed:|~T-CN$ takes [#[ [$Dmg] (~SP-DAMAGE$) + (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ]#] ~SP-DAMAGETYPE$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -ne '' -and ~SP-MORECANTRIPDAMAGE$ -eq 1 -and ~S-L$ -gt 11 -and ~S-L$ -lt 17]?? $Save < ~S-SSDC$ ?? Save Failed:|~T-CN$ takes [#[ [$Dmg] (~SP-DAMAGE$) + (~SP-DAMAGE$) + (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ]#] ~SP-DAMAGETYPE$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -ne '' -and ~SP-MORECANTRIPDAMAGE$ -eq 1 -and ~S-L$ -gt 16 ]?? $Save < ~S-SSDC$ ?? Save Failed:|~T-CN$ takes [#[ [$Dmg] (~SP-DAMAGE$) + (~SP-DAMAGE$) + (~SP-DAMAGE$) + (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ]#] ~SP-DAMAGETYPE$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -ne '']?? $Save >= ~S-SSDC$ ?? Save Succeded|~T-CN$ avoids the effect of the ~SP-NAME$ --Description|~SP-DESCRIPTION$
SpellCast: --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-ATTACK$' -ne 'None']Attack:|[#[ [$Atk] ~0! + ~S-SAB$ [Spell Attack] ]#] vs **AC** [#[~T-AC$]#] --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-ATTACK$' -eq 'None']hroll|[#[ [$Atk] 1d10 + 500 ]#] vs **AC** [#[~T-AC$]#] --['~SP-OUTPUT$' -eq 'ATTACK']?? $Atk < ~T-AC$ ?? !Missed|**You missed!** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-DAMAGE2$' -eq '']?? $Atk >= ~T-AC$ AND $Atk.base <> 20 ?? Hit:|[#[ [$Dmg] (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] + (~SP-HLDICE$*~SSL$)~SP-HLDIETYPE$~SP-HLBONUS$ [HigherLevel] ]#] ~SP-DAMAGETYPE$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-DAMAGE2$' -eq '']?? $Atk.base == 20 ?? Critical Hit:|[#[ [$Dmg] (~SP-DAMAGE$) [Base] + (~SP-DAMAGE$) [Crit] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] + (~SP-HLDICE$*~SSL$)~SP-HLDIETYPE$~SP-HLBONUS$ [HigherLevel] + (~SP-HLDICE$*~SSL$)~SP-HLDIETYPE$~SP-HLBONUS$ [HLCrit] ]#] ~SP-DAMAGETYPE$ Damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-DAMAGE2$' -ne '']?? $Atk >= ~T-AC$ AND $Atk.base <> 20 ?? Hit:|[#[ [$Dmg] (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] + (~SP-HLDICE$*~SSL$)~SP-HLDIETYPE$~SP-HLBONUS$ [HigherLevel] ]#] ~SP-DAMAGETYPE$ damage and [#[ [$Dmg2] (~SP-DAMAGE2$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ]#] ~SP-DAMAGETYPE2$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-DAMAGE2$' -ne '']?? $Atk.base == 20 ?? Critical Hit:|[#[ [$Dmg] (~SP-DAMAGE$) [Base] + (~SP-DAMAGE$) [Crit] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] + (~SP-HLDICE$*~SSL$)~SP-HLDIETYPE$~SP-HLBONUS$ [HigherLevel] + (~SP-HLDICE$*~SSL$)~SP-HLDIETYPE$~SP-HLBONUS$ [HLCrit] ]#] ~SP-DAMAGETYPE$ Damage and [#[ [$Dmg2] (~SP-DAMAGE2$) [Base] (~SP-DAMAGE2$) [Crit] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ]#] ~SP-DAMAGETYPE2$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq 'Strength']Save|~T-CN$ rolls [[ [$Save] 1d20 + ~T-SBSTR$ ]] on a ~SP-SAVE$ save vs **DC ~S-SSDC$** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq 'Dexterity']Save|~T-CN$ rolls [[ [$Save] 1d20 + ~T-SBDEX$ ]] on a ~SP-SAVE$ save vs **DC ~S-SSDC$** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq 'Constitution']Save|~T-CN$ rolls [[ [$Save] 1d20 + ~T-SBCON$ ]] on a ~SP-SAVE$ save vs **DC ~S-SSDC$** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq 'Intelligence']Save|~T-CN$ rolls [[ [$Save] 1d20 + ~T-SBINT$ ]] on a ~SP-SAVE$ save vs **DC ~S-SSDC$** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq 'Wisdom']Save|~T-CN$ rolls [[ [$Save] 1d20 + ~T-SBWIS$ ]] on a ~SP-SAVE$ save vs **DC ~S-SSDC$** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -eq 'Charisma']Save|~T-CN$ rolls [[ [$Save] 1d20 + ~T-SBCHA$ ]] on a ~SP-SAVE$ save vs **DC ~S-SSDC$** --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -ne '']?? Save < ~S-SSDC$ ?? Save Failed|~T-CN$ takes [#[ [$Dmg] (~SP-DAMAGE$) [Base] + (~S-SAM$ * ~SP-ADDABILITY$) [Ability] ]#] ~SP-DAMAGETYPE$ damage --['~SP-OUTPUT$' -eq 'ATTACK' -and '~SP-SAVE$' -ne '']?? $Save >= ~S-SSDC$ ?? Save Succeded|~T-CN$ saves for ~SP-SAVESUCCESS$ --Description|~SP-DESCRIPTION$
`;
var pc_npcaction = `!power {{
--replacenpcaction|@{selected|character_id}|?{Select action to use|@{selected|npcactionlist}}
--replacement|Advantage
--replaceattrs|S-|@{selected|character_id}
--replaceattrs|T-|@{target|character_id}
--template|NPCBasics|@{selected|token_id};@{target|token_id};uses ~NPCA-NAME$;~NPCA-NAME$;~NPCA-ATYPE$;~NPCA-RANGE$
--template|NPCAttack|~@{selected|rtype}$
}}`;
var pc_pcattack = `!power {{
--replacepcattack|@{selected|character_id}|?{Select attack to use|@{selected|attacklist}}
--replacement|Advantage
--replaceattrs|S-|@{selected|character_id}
--replaceattrs|T-|@{target|character_id}
--template|Basics|@{selected|token_id};@{target|token_id};uses ~PCA-NAME$;~PCA-NAME$;~PCA-ATYPE$;~PCA-RANGE$
--template|PCAttack|~@{selected|rtype}$
}}`;
var pc_legendary = `!power {{
--replacenpcaction|@{selected|character_id}|?{Select action to use|@{selected|legendaryactionlist}}
--replacement|Advantage
--replaceattrs|S-|@{selected|character_id}
--replaceattrs|T-|@{target|character_id}
--template|NPCBasics|@{selected|token_id};@{target|token_id};uses ~NPCA-NAME$;~NPCA-NAME$;~NPCA-ATYPE$;~NPCA-RANGE$
--template|NPCAttack|~@{selected|rtype}$
}}`;
var pc_cantrip = `!power {{
--replacespell|@{selected|character_id}|?{Cantrip to cast|@{selected|cantrip_list}}
--replaceattrs|S-|@{selected|character_id} --replaceattrs|T-|@{target|character_id} --replacement|Advantage
--template|Basics|@{selected|token_id};@{target|token_id};casts ~SP-NAME$;~SP-NAME$;Cantrip;~SP-RANGE$
--template|CantripAttack|~@{selected|rtype}$
}}`;
var pc_level1 = `!power {{
--replacespell|@{selected|character_id}|?{Spell to Cast|@{selected|l1_spell_list}}
--inlinereplace|SSL|?{Spell Slot Level?|1,0|2,1|3,2|4,3|5,4|6,5|7,6|8,7|9,8}
--replaceattrs|S-|@{selected|character_id} --replaceattrs|T-|@{target|character_id} --replacement|Advantage
--template|Basics|@{selected|token_id};@{target|token_id};casts ~SP-NAME$;~SP-NAME$;Save DC ~S-SSDC$;~SP-RANGE$
--template|SpellCast|~@{selected|rtype}$
}}`;
var pc_level2 = `!power {{
--replacespell|@{selected|character_id}|?{Spell to Cast|@{selected|l2_spell_list}}
--inlinereplace|SSL|?{Spell Slot Level?|2,0|3,1|4,2|5,3|6,4|7,5|8,6|9,7}
--replaceattrs|S-|@{selected|character_id} --replaceattrs|T-|@{target|character_id} --replacement|Advantage
--template|Basics|@{selected|token_id};@{target|token_id};casts ~SP-NAME$;~SP-NAME$;Save DC ~S-SSDC$;~SP-RANGE$
--template|SpellCast|~@{selected|rtype}$
}}`;
var pc_level3 = `!power {{
--replacespell|@{selected|character_id}|?{Spell to Cast|@{selected|l3_spell_list}}
--inlinereplace|SSL|?{Spell Slot Level?|3,0|4,1|5,2|6,3|7,4|8,5|9,6}
--replaceattrs|S-|@{selected|character_id} --replaceattrs|T-|@{target|character_id} --replacement|Advantage
--template|Basics|@{selected|token_id};@{target|token_id};casts ~SP-NAME$;~SP-NAME$;Save DC ~S-SSDC$;~SP-RANGE$
--template|SpellCast|~@{selected|rtype}$
}}`;
var pc_level4 = `!power {{
--replacespell|@{selected|character_id}|?{Spell to Cast|@{selected|l4_spell_list}}
--inlinereplace|SSL|?{Spell Slot Level?|4,0|5,1|6,2|7,3|8,4|9,5}
--replaceattrs|S-|@{selected|character_id} --replaceattrs|T-|@{target|character_id} --replacement|Advantage
--template|Basics|@{selected|token_id};@{target|token_id};casts ~SP-NAME$;~SP-NAME$;Save DC ~S-SSDC$;~SP-RANGE$
--template|SpellCast|~@{selected|rtype}$
}}`;
var pc_level5 = `!power {{
--replacespell|@{selected|character_id}|?{Spell to Cast|@{selected|l5_spell_list}}
--inlinereplace|SSL|?{Spell Slot Level?|5,0|6,1|7,2|8,3|9,4}
--replaceattrs|S-|@{selected|character_id} --replaceattrs|T-|@{target|character_id} --replacement|Advantage
--template|Basics|@{selected|token_id};@{target|token_id};casts ~SP-NAME$;~SP-NAME$;Save DC ~S-SSDC$;~SP-RANGE$
--template|SpellCast|~@{selected|rtype}$
}}`;
var pc_level6 = `!power {{
--replacespell|@{selected|character_id}|?{Spell to Cast|@{selected|l6_spell_list}}
--inlinereplace|SSL|?{Spell Slot Level?|6,0|7,1|8,2|9,3}
--replaceattrs|S-|@{selected|character_id} --replaceattrs|T-|@{target|character_id} --replacement|Advantage
--template|Basics|@{selected|token_id};@{target|token_id};casts ~SP-NAME$;~SP-NAME$;Save DC ~S-SSDC$;~SP-RANGE$
--template|SpellCast|~@{selected|rtype}$
}}`;
var pc_level7 = `!power {{
--replacespell|@{selected|character_id}|?{Spell to Cast|@{selected|l7_spell_list}}
--inlinereplace|SSL|?{Spell Slot Level?|7,0|8,1|9,2}
--replaceattrs|S-|@{selected|character_id} --replaceattrs|T-|@{target|character_id} --replacement|Advantage
--template|Basics|@{selected|token_id};@{target|token_id};casts ~SP-NAME$;~SP-NAME$;Save DC ~S-SSDC$;~SP-RANGE$
--template|SpellCast|~@{selected|rtype}$
}}`;
var pc_level8 = `!power {{
--replacespell|@{selected|character_id}|?{Spell to Cast|@{selected|l8_spell_list}}
--inlinereplace|SSL|?{Spell Slot Level?|8,0|9,1}
--replaceattrs|S-|@{selected|character_id} --replaceattrs|T-|@{target|character_id} --replacement|Advantage
--template|Basics|@{selected|token_id};@{target|token_id};casts ~SP-NAME$;~SP-NAME$;Save DC ~S-SSDC$;~SP-RANGE$
--template|SpellCast|~@{selected|rtype}$
}}`;
var pc_level9 = `!power {{
--replacespell|@{selected|character_id}|?{Spell to Cast|@{selected|l9_spell_list}}
--inlinereplace|SSL|0
--replaceattrs|S-|@{selected|character_id} --replaceattrs|T-|@{target|character_id} --replacement|Advantage
--template|Basics|@{selected|token_id};@{target|token_id};casts ~SP-NAME$;~SP-NAME$;Save DC ~S-SSDC$;~SP-RANGE$
--template|SpellCast|~@{selected|rtype}$
}}`;
// API COMMAND HANDLER
on("chat:message", function(msg) {
if (msg.type !== "api") {
return;
}
switch (msg.content.split(" ", 1)[0].toLowerCase()) {
case "!PCMHelper":
case "!pcmhelper":
case "!PCMHELPER":
case "!PCM":
case "!pcm":
var player_obj = getObj("player", msg.playerid);
msg.who = msg.who.replace(" (GM)", "");
msg.content = msg.content.replace(/
\n/g, ' ').replace(/({{)(.*)((\}\}))/, " $2 ").replace(/\[#\[/g,"[[").replace(/\]#\]/g,"]]");
getAttackInfo(msg, player_obj);
getNPCActionInfo(msg, player_obj);
getSpellInfo(msg, player_obj);
break;
case "!pcmsetup":
createHandouts();
createPCMacros(getObj("player", msg.playerid));
break;
case "!pcmqueue":
processQueue();
break;
case "!pcmeverybody":
var chars = findObjs({ _type: "character"});
_.each(chars, function(obj) {
log("Processing character: " + obj.get("_id"));
processCharacter(obj.get("_id"));
processNPC(obj.get("_id"));
processSpells(obj.get("_id"));
});
break;
case "!pcm_version":
sendChat("", "/w " + msg.who + " You are using version " + PCMHelper_Version + " of PCMHelper, authored by " + PCMHelper_Author + ", which was last updated on: " + PCMHelper_LastUpdated + ".");
break;
}
});
function processQueue() {
if (state.PCMQueue !== "") {
var queueList = state.PCMQueue.split("|");
for (var x=0; x PCMHelper v" + PCMHelper_Version + " <=- [" + PCMHelper_LastUpdated + "]");
});
function processCharacter(charID)
{
// Only run on tokens that represent characters (PCs or NPCs)
var atklist = "";
var attrCount = 0;
var attrs = getRepeatingSectionAttrs(charID, "repeating_attack");
var attrLen = 0;
if (attrs !== undefined) {
attrLen = attrs[0].length;
}
if (attrLen > 0) {
do {
var key="repeating_attack_$" + attrCount + "_atkname";
thisAttr = getAttrByName(charID, "repeating_attack_$" + attrCount + "_atkname");
var isAttack = getAttrByName(charID, "repeating_attack_$" + attrCount + "_atkflag");
if (isAttack !== "0") {
if (thisAttr !== "") {
atklist += thisAttr + "|";
}
}
attrCount += 1;
} while (thisAttr !== "" && attrCount < attrLen);
}
oldAttrs = findObjs({ _type:"attribute", _characterid:charID, name: "attacklist"});
if (oldAttrs.length > 0) {
oldAttrs.forEach(function(element) { element.remove(); });
}
if (atklist !== "") {
atklist = atklist.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "attacklist", current: atklist });
}
}
function getAttackInfo(msg, player_obj) {
// Run through all selected tokens
msg.selected.forEach(function(selected){
var tokenID;
var graphicObj;
var charObj;
var charID;
// Retrieve information about what this token represents
tokenID = selected._id;
if (tokenID !== undefined) {
graphicObj = getObj("graphic", tokenID);
}
if (graphicObj !== undefined) {
charObj = getObj("character", graphicObj.get("represents"));
}
if (charObj !== undefined) {
charID = charObj.get("_id");
}
if (charID !== undefined) {
processCharacter(charID);
}
});
}
function processNPC(charID)
{
// Only run on tokens that represent characters (PCs or NPCs)
var attrCount = 0;
var actionList = "";
var legendaryList = "";
var attrs = getRepeatingSectionAttrs(charID, "repeating_npcaction");
var lattrs = getRepeatingSectionAttrs(charID, "repeating_npcaction-l");
var attrLen = 0;
var lattrLen = 0;
if (attrs !== undefined) {
attrLen = attrs[0].length;
}
if (lattrs !== undefined) {
lattrLen = lattrs[0].length;
}
if (attrLen > 0) {
do {
var key="repeating_npcaction_$" + attrCount + "_atkname";
thisAttr = getAttrByName(charID, "repeating_npcaction_$" + attrCount + "_name");
if (thisAttr !== "") {
actionList += thisAttr + "|";
}
attrCount += 1;
} while (thisAttr !== "" && attrCount < attrLen);
}
var attrCount = 0;
if (lattrLen > 0) {
do {
var key="repeating_npcaction-l_$" + attrCount + "_atkname";
thisAttr = getAttrByName(charID, "repeating_npcaction-l_$" + attrCount + "_name");
if (thisAttr !== "") {
legendaryList += thisAttr + "|";
}
attrCount += 1;
} while (thisAttr !== "" && attrCount < lattrLen);
}
oldAttrs = findObjs({ _type:"attribute", _characterid:charID, name: "npcactionlist"});
if (oldAttrs.length > 0) {
oldAttrs.forEach(function(element) { element.remove(); });
}
oldAttrs = findObjs({ _type:"attribute", _characterid:charID, name: "legendaryactionlist"});
if (oldAttrs.length > 0) {
oldAttrs.forEach(function(element) { element.remove(); });
}
if (actionList !== "") {
actionList = actionList.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "npcactionlist", current: actionList });
}
if (legendaryList !== "") {
legendaryList = legendaryList.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "legendaryactionlist", current: legendaryList });
}
}
function processSpells(charID) {
var spell_list = filterObjs(function(z) { return (z.get("characterid") == charID && z.get("name").endsWith("spellname")); });
var cantrips = sortByKey(spell_list.filter(s => s.get("name").startsWith("repeating_spell-cantrip")), "current");
var L1Spells = sortByKey(spell_list.filter(s => s.get("name").startsWith("repeating_spell-1")), "current");
var L2Spells = sortByKey(spell_list.filter(s => s.get("name").startsWith("repeating_spell-2")), "current");
var L3Spells = sortByKey(spell_list.filter(s => s.get("name").startsWith("repeating_spell-3")), "current");
var L4Spells = sortByKey(spell_list.filter(s => s.get("name").startsWith("repeating_spell-4")), "current");
var L5Spells = sortByKey(spell_list.filter(s => s.get("name").startsWith("repeating_spell-5")), "current");
var L6Spells = sortByKey(spell_list.filter(s => s.get("name").startsWith("repeating_spell-6")), "current");
var L7Spells = sortByKey(spell_list.filter(s => s.get("name").startsWith("repeating_spell-7")), "current");
var L8Spells = sortByKey(spell_list.filter(s => s.get("name").startsWith("repeating_spell-8")), "current");
var L9Spells = sortByKey(spell_list.filter(s => s.get("name").startsWith("repeating_spell-9")), "current");
nameList = "cantrip_list|l1_spell_list|l2_spell_list|l3_spell_list|l4_spell_list|l5_spell_list|l6_spell_list|l7_spell_list|l8_spell_list|l9_spell_list";
var attrNames = nameList.split("|");
attrNames.forEach(function(attrName) {
oldAttrs = findObjs({ _type:"attribute", _characterid:charID, name: attrName} );
if (oldAttrs.length > 0)
{
oldAttrs.forEach(function(element) { element.remove(); });
}
});
var attrText = "";
if (cantrips.length > 0) {
cantrips.forEach(function(s) { attrText += s.get("current") + "|" });
attrText = attrText.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "cantrip_list", current: attrText });
}
attrText = "";
if (L1Spells.length > 0) {
L1Spells.forEach(function(s) { attrText += s.get("current") + "|" });
attrText = attrText.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "l1_spell_list", current: attrText });
}
attrText = "";
if (L2Spells.length > 0) {
L2Spells.forEach(function(s) { attrText += s.get("current") + "|" });
attrText = attrText.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "l2_spell_list", current: attrText });
}
attrText = "";
if (L3Spells.length > 0) {
L3Spells.forEach(function(s) { attrText += s.get("current") + "|" });
attrText = attrText.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "l3_spell_list", current: attrText });
}
attrText = "";
if (L4Spells.length > 0) {
L4Spells.forEach(function(s) { attrText += s.get("current") + "|" });
attrText = attrText.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "l4_spell_list", current: attrText });
}
attrText = "";
if (L5Spells.length > 0) {
L5Spells.forEach(function(s) { attrText += s.get("current") + "|" });
attrText = attrText.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "l5_spell_list", current: attrText });
}
attrText = "";
if (L6Spells.length > 0) {
L6Spells.forEach(function(s) { attrText += s.get("current") + "|" });
attrText = attrText.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "l6_spell_list", current: attrText });
}
attrText = "";
if (L7Spells.length > 0) {
L7Spells.forEach(function(s) { attrText += s.get("current") + "|" });
attrText = attrText.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "l7_spell_list", current: attrText });
}
attrText = "";
if (L8Spells.length > 0) {
L8Spells.forEach(function(s) { attrText += s.get("current") + "|" });
attrText = attrText.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "l8_spell_list", current: attrText });
}
attrText = "";
if (L9Spells.length > 0) {
L9Spells.forEach(function(s) { attrText += s.get("current") + "|" });
attrText = attrText.slice(0,-1);
createObj("attribute", { _characterid: charID, name: "l9_spell_list", current: attrText });
}
}
function getNPCActionInfo(msg, player_obj) {
// Run through all selected tokens
msg.selected.forEach(function(selected){
var tokenID;
var graphicObj;
var charObj;
var charID;
// Retrieve information about what this token represents
tokenID = selected._id;
if (tokenID !== undefined) {
graphicObj = getObj("graphic", tokenID);
}
if (graphicObj !== undefined) {
charObj = getObj("character", graphicObj.get("represents"));
}
if (charObj !== undefined) {
charID = charObj.get("_id");
}
if (charID !== undefined) {
processNPC(charID);
}
});
}
function getSpellInfo(msg, player_obj) {
msg.selected.forEach(function(selected){
var tokenID;
var graphicObj;
var charObj;
var charID;
tokenID = selected._id;
if (tokenID !== undefined) {
graphicObj = getObj("graphic", tokenID);
}
if (graphicObj !== undefined) {
charObj = getObj("character", graphicObj.get("represents"));
}
if (charObj !== undefined) {
charID = charObj.get("_id");
}
if (charID !== undefined) {
processSpells(charID);
}
});
}
function sortByKey(array, key) {
return array.sort(function(a, b) {
var x = a.get(key);
var y = b.get(key);
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
const getRepeatingSectionAttrs = function (charid, prefix) {
// Input
// charid: character id
// prefix: repeating section name, e.g. 'repeating_weapons'
// Output
// repRowIds: array containing all repeating section IDs for the given prefix, ordered in the same way that the rows appear on the sheet
// repeatingAttrs: object containing all repeating attributes that exist for this section, indexed by their name
const repeatingAttrs = {},
regExp = new RegExp(`^${prefix}_(-[-A-Za-z0-9]+?|\\d+)_`);
let repOrder;
// Get attributes
findObjs({
_type: 'attribute',
_characterid: charid
}).forEach(o => {
const attrName = o.get('name');
if (attrName.search(regExp) === 0) repeatingAttrs[attrName] = o;
else if (attrName === `_reporder_${prefix}`) repOrder = o.get('current').split(',');
});
if (!repOrder) repOrder = [];
// Get list of repeating row ids by prefix from repeatingAttrs
const unorderedIds = [...new Set(Object.keys(repeatingAttrs)
.map(n => n.match(regExp))
.filter(x => !!x)
.map(a => a[1]))];
const repRowIds = [...new Set(repOrder.filter(x => unorderedIds.includes(x)).concat(unorderedIds))];
return [repRowIds, repeatingAttrs];
}
function createHandouts() {
// Start by removing existing handouts if they are in the game
var Handouts = findObjs({
_type : "handout",
});
Handouts.forEach(function (handout) {
if (handout.get("name").startsWith("PowerCard Replacements PCMHelper")) {
handout.remove();
};
if (handout.get("name").startsWith("PowerCard Templates PCMHelper")) {
handout.remove();
};
});
// Create the handouts. Note the because of an API bug, the Notes cannot be set
// during creation and if an attempt is made to do that, they are bugged and can't
// be set after creation either, so create them empty.
createObj("handout", { name:"PowerCard Replacements PCMHelper"});
createObj("handout", { name:"PowerCard Templates PCMHelper"});
// Now loop through the handouts again and set the notes for them.
Handouts = findObjs({
_type : "handout",
});
Handouts.forEach(function (handout) {
if (handout.get("name").startsWith("PowerCard Replacements PCMHelper")) {
handout.set("notes", pc_replacements);
};
if (handout.get("name").startsWith("PowerCard Templates PCMHelper")) {
handout.set("notes", pc_templates);
};
});
}
function createPCMacros(player) {
var pid = player.get("id");
var Macros = findObjs({
_type : "macro",
});
Macros.forEach(function (macro) {
if (macro.get("name") === "NPC-Action") {
macro.remove();
}
if (macro.get("name") === "NPC-Legendary") {
macro.remove();
}
if (macro.get("name") === "PC-Attack") {
macro.remove();
}
if (macro.get("name") === "Cast-Cantrip") {
macro.remove();
}
if (macro.get("name") === "Cast-L1") {
macro.remove();
}
if (macro.get("name") === "Cast-L2") {
macro.remove();
}
if (macro.get("name") === "Cast-L3") {
macro.remove();
}
if (macro.get("name") === "Cast-L4") {
macro.remove();
}
if (macro.get("name") === "Cast-L5") {
macro.remove();
}
if (macro.get("name") === "Cast-L6") {
macro.remove();
}
if (macro.get("name") === "Cast-L7") {
macro.remove();
}
if (macro.get("name") === "Cast-L8") {
macro.remove();
}
if (macro.get("name") === "Cast-L9") {
macro.remove();
}
});
createObj("macro", { name:"NPC-Action", playerid:pid });
createObj("macro", { name:"NPC-Legendary", playerid:pid });
createObj("macro", { name:"PC-Attack", playerid:pid, visibleto:"all", istokenaction:true });
createObj("macro", { name:"Cast-Cantrip", playerid:pid, visibleto:"all", istokenaction:true });
createObj("macro", { name:"Cast-L1", playerid:pid, visibleto:"all", istokenaction:true });
createObj("macro", { name:"Cast-L2", playerid:pid, visibleto:"all", istokenaction:true });
createObj("macro", { name:"Cast-L3", playerid:pid, visibleto:"all", istokenaction:true });
createObj("macro", { name:"Cast-L4", playerid:pid, visibleto:"all", istokenaction:true });
createObj("macro", { name:"Cast-L5", playerid:pid, visibleto:"all", istokenaction:true });
createObj("macro", { name:"Cast-L6", playerid:pid, visibleto:"all", istokenaction:true });
createObj("macro", { name:"Cast-L7", playerid:pid, visibleto:"all", istokenaction:true });
createObj("macro", { name:"Cast-L8", playerid:pid, visibleto:"all", istokenaction:true });
createObj("macro", { name:"Cast-L9", playerid:pid, visibleto:"all", istokenaction:true });
var Macros = findObjs({
_type : "macro",
});
Macros.forEach(function (macro) {
if (macro.get("name") === "NPC-Action") {
macro.set("action", pc_npcaction);
}
if (macro.get("name") === "NPC-Legendary") {
macro.set("action", pc_legendary);
}
if (macro.get("name") === "PC-Attack") {
macro.set("action", pc_pcattack);
}
if (macro.get("name") === "Cast-Cantrip") {
macro.set("action", pc_cantrip);
}
if (macro.get("name") === "Cast-L1") {
macro.set("action", pc_level1);
}
if (macro.get("name") === "Cast-L2") {
macro.set("action", pc_level2);
}
if (macro.get("name") === "Cast-L3") {
macro.set("action", pc_level3);
}
if (macro.get("name") === "Cast-L4") {
macro.set("action", pc_level4);
}
if (macro.get("name") === "Cast-L5") {
macro.set("action", pc_level5);
}
if (macro.get("name") === "Cast-L6") {
macro.set("action", pc_level6);
}
if (macro.get("name") === "Cast-L7") {
macro.set("action", pc_level7);
}
if (macro.get("name") === "Cast-L8") {
macro.set("action", pc_level8);
}
if (macro.get("name") === "Cast-L9") {
macro.set("action", pc_level9);
}
});
}
})();